// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024.
* Description: dm functions for vims
*/

#include "dm-mpath.h"
#include "dm-core.h"
#include "dm-rq.h"
#include "dm-uevent.h"
#include "dm-bio-record.h"
#include "dm-path-selector.h"

#include <scsi/scsi_dh.h>
#include <scsi/scsi_eh.h>
#include <linux/device-mapper.h>
#include <linux/blkdev.h>
#include <linux/ctype.h>
#include <linux/init.h>
#include <linux/mempool.h>
#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/blk-mq.h>
#include <linux/mutex.h>
#include <linux/dm-vims.h>

#define DM_MSG_PREFIX "vims"

static bool blk_path_retry_check;
module_param(blk_path_retry_check, bool, 0600);

static int check_retry(struct request *rq, blk_status_t error)
{
#define PRO_PREEMPT_AND_ABORT 0x05
	struct scsi_sense_hdr sshdr;
	struct scsi_request *req;

	if (!rq)
		return 0;

	req = scsi_req(rq);
	if (!req)
		return 0;

	/* Don't retry for sg_persist PREEMPT AND ABORT */
	if (error == BLK_STS_NEXUS && req->cmd[0] == PERSISTENT_RESERVE_OUT &&
	    (req->cmd[1] & 0x1f) == PRO_PREEMPT_AND_ABORT)
		return 1;

	/* Don't retry for illegal WRITE SAME */
	if (req->cmd[0] == WRITE_SAME || req->cmd[0] == WRITE_SAME_16 ||
	    req->cmd[0] == WRITE_SAME_32) {
		if (scsi_normalize_sense(req->sense, req->sense_len, &sshdr) &&
		    sshdr.sense_key == ILLEGAL_REQUEST) {
			return 1;
		}
	}

	/* Don't retry for compare and write command */
	if (req->cmd[0] == COMPARE_AND_WRITE)
		return 1;

	/* Anything else could be a path failure, so should be retried */
	return 0;
}

int blk_path_check_retry(struct request *rq, blk_status_t error)
{
	if (blk_path_retry_check && check_retry(rq, error))
		return 1;

	return 0;
}
EXPORT_SYMBOL_GPL(blk_path_check_retry);

static void free_dev_segment(struct list_head *dev_list)
{
	struct dev_segment *dev_seg = NULL, *tmp = NULL;

	list_for_each_entry_safe(dev_seg, tmp, dev_list, list) {
		list_del_init(&dev_seg->list);
		kfree(dev_seg);
	}
}

static int fill_dev_segment(struct mapped_device *md, struct dm_target *ti,
			    struct dev_segment *dev_seg,
			    struct list_head *dev_list)
{
	struct bio tmp_bio = { 0 };
	struct block_device *bdev = NULL;
	sector_t sector;
	bool need_put_flag = false;

	if (strncmp(ti->type->name, "linear", strlen("linear")) == 0) {
		tmp_bio.bi_iter.bi_size = to_bytes(1);
		tmp_bio.bi_iter.bi_sector = ti->begin;
		ti->type->map(ti, &tmp_bio);
		bdev = bdget_disk(tmp_bio.bi_disk, tmp_bio.bi_partno);
		if (!bdev) {
			DMWARN("%s get block device null.", md->name);
			return -EINVAL;
		}
		need_put_flag = true;
		sector = tmp_bio.bi_iter.bi_sector;
	} else if (strncmp(ti->type->name, "multipath", strlen("multipath")) ==
		   0) {
		/* Refer to the dm_prepare_ioctl function */
		if (!ti->type->prepare_ioctl) {
			DMWARN("%s prepare_ioctl is NULL.", md->name);
			return -ENOTTY;
		}
		if (ti->type->prepare_ioctl(ti, &bdev) < 0 || !bdev) {
			DMWARN("%s prepare_ioctl error.", md->name);
			return -EINVAL;
		}
		if (!strncmp(bdev->bd_disk->disk_name, "dm-", strlen("dm-"))) {
			DMWARN("%s god! here get the segment device is mapped_device: %s",
			       md->name, bdev->bd_disk->disk_name);
			return -EINVAL;
		}
		sector = ti->begin;
	} else {
		DMWARN("%s unknown target type.", md->name);
		return -EINVAL;
	}

	dev_seg->dvd = bdev->bd_dev;
	memcpy(dev_seg->disk_name, bdev->bd_disk->disk_name,
	       sizeof(bdev->bd_disk->disk_name));
	dev_seg->start = to_bytes(sector);
	dev_seg->len = to_bytes(ti->len);
	list_add_tail(&dev_seg->list, dev_list);
	if (need_put_flag)
		bdput(bdev);
	return 0;
}

int dm_get_device_segment(dev_t dev, struct list_head *dev_list)
{
	struct mapped_device *md;
	struct dm_table *table = NULL;
	struct dm_target *ti;
	struct dev_segment *dev_seg = NULL;
	int ret = -EINVAL;
	unsigned int index = 0, num_targets;
	int srcu_idx;

	if (!list_empty(dev_list)) {
		DMWARN("device list is not NULL.");
		return ret;
	}

	md = dm_get_md(dev);
	if (!md) {
		ret = -ENOENT;
		return ret;
	}

	if (dm_suspended_md(md)) {
		DMWARN("%s mapped device is suspended.", md->name);
		ret = -EAGAIN;
		goto out;
	}

	table = dm_get_live_table(md, &srcu_idx);
	if (!table) {
		DMWARN("%s get live table failed", md->name);
		goto out;
	}

	num_targets = dm_table_get_num_targets(table);

	while (index < num_targets) {
		ti = dm_table_get_target(table, index);
		if (!ti) {
			DMWARN("%s dm target is invalid.", md->name);
			ret = -EINVAL;
			goto out;
		}

		dev_seg = kzalloc(sizeof(struct dev_segment), GFP_KERNEL);
		if (!dev_seg) {
			DMWARN("%s alloc dev segment failed.", md->name);
			ret = -ENOMEM;
			goto out;
		}

		ret = fill_dev_segment(md, ti, dev_seg, dev_list);
		if (ret < 0) {
			DMWARN("%s fill dev segment failed.", md->name);
			kfree(dev_seg);
			goto out;
		}

		index++;
	}

	ret = 0;

out:
	if (ret < 0)
		free_dev_segment(dev_list);
	if (table)
		dm_put_live_table(md, srcu_idx);
	if (md)
		dm_put(md);

	return ret;
}
EXPORT_SYMBOL_GPL(dm_get_device_segment);
